#include "stdafx.h"
#include "MemoryPool.h"
#include <iostream>

using namespace std;

MemoryPool* MemoryPool::m_pcMemoryPool = NULL;

MemoryPool* MemoryPool::Instance()
{
	if(NULL == m_pcMemoryPool)
	{
		m_pcMemoryPool = new MemoryPool(DEFAULT_SIZE,DEFAULT_UNIT_SIZE,DEFAULT_UNITS_IN_PAGE,DEFAULT_GROW_SIZE);
		if(NULL == m_pcMemoryPool)
		{
			LOG(FATAL) << "Instance of memory pool is NULL";
		}
	}

	return m_pcMemoryPool;
}

MemoryPool* MemoryPool::Instance(unsigned int p_uiMemorySize,unsigned int p_uiUnitSize,unsigned int p_uiUnitInPage,unsigned int p_uiGrowSize)
{
	if(NULL == m_pcMemoryPool)
	{
		// [TODO]Verify the parameters

		m_pcMemoryPool = new MemoryPool(p_uiMemorySize,p_uiUnitSize,p_uiUnitInPage,p_uiGrowSize);
		if(NULL == m_pcMemoryPool)
		{
			LOG(FATAL) << "Instance of memory pool is NULL";
		}
	}

	LOG(WARNING) << "Memory pool instance is already exist. Parameters will be ignored";
	return m_pcMemoryPool;
}

MemoryPool::MemoryPool(unsigned int p_uiMemorySize,unsigned int p_uiUnitSize,unsigned int p_uiUnistInPage,unsigned int p_uiGrowSize)
	:m_uiMemorySize(p_uiMemorySize)
	,m_uiUnitSize(p_uiUnitSize)
	,m_uiUnitsInPage(p_uiUnistInPage)
	,m_uiGrowSize(p_uiGrowSize)
	,m_uiMaxIndex(0)
{
	// Construct memory pool
	int iCount = m_uiMemorySize / (m_uiUnitSize * m_uiUnitsInPage);
	for(int i = 0; i < iCount; i++)
	{
		// Define a page
		PageInfo stPage;
		char* pszAddress = (char*)malloc(m_uiUnitSize * m_uiUnitsInPage);
		if(NULL == pszAddress)
		{
			LOG(FATAL) << "Cannot allocate memory";
		}

		stPage.pszPageAddress = pszAddress;
		for(int j = 0; j < m_uiUnitsInPage; j++)
		{
			// Define a unit
			UnitInfo stUnit;
			stUnit.m_uiIndex = m_uiMaxIndex;
			stUnit.pszAddress = pszAddress + (j * m_uiUnitSize);

			// Add an unit information to page
			stPage.m_vecUnitList.push_back(stUnit);
			m_uiMaxIndex++;
		}

		// Add a page to memory pool
		m_vecPageInfo.push_back(stPage);
	}

	m_uiUnitsCount = m_uiMemorySize / m_uiUnitSize;
	m_uiUsedUnits = 0;

	// Initialize critical section
	InitializeCriticalSection(&m_csLock);
}

char* MemoryPool::VerifyID(unsigned int p_uiID)
{
	list<IndexTable>::iterator it = m_lstIndexTable.begin();
	
	for(; it != m_lstIndexTable.end(); it++)
	{
		if(it->m_uiID == p_uiID)
		{
			it->m_uiRefCount++;
			return it->m_pszAddress;
		}
	}

	return NULL;
}

char* MemoryPool::Allocate(unsigned int p_uiBufferSize, unsigned int p_uiID)
{
	EnterCriticalSection(&m_csLock);

	// Add reference count if ID is already existed in index table
	char* pszAddress = VerifyID(p_uiID);
	if(NULL != pszAddress)
	{
		LeaveCriticalSection(&m_csLock);
		return pszAddress;
	}

	if(p_uiBufferSize <= m_uiUnitSize)
	{// Just single unit to be required
		pszAddress = AllocateSingleUnit(p_uiBufferSize, p_uiID);
	}
	else
	{// Multiple units to be required
		pszAddress = AllocateMultiUnit(p_uiBufferSize, p_uiID);
	}

	LeaveCriticalSection(&m_csLock);
	return pszAddress;
}

char* MemoryPool::AllocateSingleUnit(unsigned int p_uiBufferSize, unsigned int p_uiID)
{
	// Allocate memory base on buffer size
	IndexTable stIndexInfo;
	int iCount = m_uiMemorySize / (m_uiUnitSize * m_uiUnitsInPage);

	for(int i = 0; i < iCount; i++)
	{
		for(int j = 0; j < m_uiUnitsInPage; j++)
		{
			if(m_vecPageInfo[i].m_vecUnitList[j].m_bIsAvailable == true)
			{
				// Update status of unit
				m_vecPageInfo[i].m_vecUnitList[j].m_uiUnitCount = 1;
				m_vecPageInfo[i].m_vecUnitList[j].m_bIsAvailable = false;
				
				// Construct index table
				stIndexInfo.m_pszAddress = m_vecPageInfo[i].m_vecUnitList[j].pszAddress;
				stIndexInfo.m_uiID = p_uiID;
				stIndexInfo.m_uiIndex = m_vecPageInfo[i].m_vecUnitList[j].m_uiIndex;
				stIndexInfo.m_uiRefCount = 1;
				m_lstIndexTable.push_back(stIndexInfo);

				// Update used units count
				m_uiUsedUnits++;

				return m_vecPageInfo[i].m_vecUnitList[j].pszAddress;
			}
		}
	}

	// Allocate new buffer and add in memory pool
	LOG(WARNING) << "Memory pool is full, allocating new buffer. Size : " << m_uiGrowSize;
	PageInfo page = AllocateGrowBuffer();
	if(NULL == page.pszPageAddress)
	{
		return NULL;
	}

	page.m_vecUnitList[0].m_uiUnitCount = 1;
	page.m_vecUnitList[0].m_bIsAvailable = false;

	// Construct index table
	stIndexInfo.m_pszAddress = page.m_vecUnitList[0].pszAddress;
	stIndexInfo.m_uiID = p_uiID;
	stIndexInfo.m_uiIndex = page.m_vecUnitList[0].m_uiIndex;
	stIndexInfo.m_uiRefCount = 1;
	m_lstIndexTable.push_back(stIndexInfo);

	// Update used units count
	m_uiUsedUnits++;

	return page.m_vecUnitList[0].pszAddress;
}

char* MemoryPool::AllocateMultiUnit(unsigned int p_uiBufferSize, unsigned int p_uiID)
{
	// Verify if memory pool can allocate required buffer
	unsigned int uiUnitCount = p_uiBufferSize / m_uiUnitSize;
	if(0 != p_uiBufferSize % m_uiUnitSize)
	{// Has extra size
		uiUnitCount++;
	}

	if(uiUnitCount > m_uiUnitsInPage)
	{// Required size is larger than unit count in one page
		LOG(ERROR) << "Required unit count:" << uiUnitCount << "is larger than unit count in one page:" << m_uiUnitsInPage;
		return NULL;
	}

	// Allocate memory base on buffer size
	IndexTable stIndexInfo;
	unsigned int uiCount = m_uiMemorySize / (m_uiUnitSize * m_uiUnitsInPage);
	for(unsigned int i = 0; i < uiCount; i++)
	{
		for(unsigned int j = 0; j < m_uiUnitsInPage; j++)
		{
			// Break if required unit cannot allocated in this page
			if((j + uiUnitCount) > m_uiUnitsInPage)
			{
				break;
			}

			// Verify if there is enough memory in one page
			bool bIsAvaliable = true;
			for(unsigned int uiTemp = 0; uiTemp < uiUnitCount; uiTemp++)
			{
				if(m_vecPageInfo[i].m_vecUnitList[j+uiTemp].m_bIsAvailable == false)
				{
					bIsAvaliable = false;
					break;
				}
			}

			if(bIsAvaliable == false)
			{
				continue;
			}

			// Allocate memory
			if(m_vecPageInfo[i].m_vecUnitList[j].m_bIsAvailable == true)
			{
				if (j + uiUnitCount >= m_uiUnitsInPage)
				{
					LOG(ERROR) << "Cannot allocate multiple units(" << m_uiUnitsInPage << ") in one page";
					break;
				}

				// Update status of unit
				m_vecPageInfo[i].m_vecUnitList[j].m_uiUnitCount = uiUnitCount;
				m_vecPageInfo[i].m_vecUnitList[j].m_bIsAvailable = false;
				for(unsigned int uiTemp = 1; uiTemp < uiUnitCount; uiTemp++)
				{
					// Update used units count
					m_uiUsedUnits++;
					m_vecPageInfo[i].m_vecUnitList[j+uiTemp].m_bIsAvailable = false;
				}
				
				// Construct index table
				stIndexInfo.m_pszAddress = m_vecPageInfo[i].m_vecUnitList[j].pszAddress;
				stIndexInfo.m_uiID = p_uiID;
				stIndexInfo.m_uiIndex = m_vecPageInfo[i].m_vecUnitList[j].m_uiIndex;
				stIndexInfo.m_uiRefCount = 1;
				m_lstIndexTable.push_back(stIndexInfo);

				return m_vecPageInfo[i].m_vecUnitList[j].pszAddress;
			}
		}
	}

	// Allocate new buffer and add in memory pool
	LOG(WARNING) << "Memory pool is full, allocating new buffer. Size : " << m_uiGrowSize << endl;
	PageInfo page = AllocateGrowBuffer();
	if(NULL == page.pszPageAddress)
	{
		return NULL;
	}

	page.m_vecUnitList[0].m_uiUnitCount = uiUnitCount;
	page.m_vecUnitList[0].m_bIsAvailable = false;
	for(unsigned int uiTemp = 1; uiTemp < uiUnitCount; uiTemp++)
	{
		// Update used units count
		m_uiUsedUnits++;
		page.m_vecUnitList[uiTemp].m_bIsAvailable = false;
	}

	// Construct index table
	stIndexInfo.m_pszAddress = page.m_vecUnitList[0].pszAddress;
	stIndexInfo.m_uiID = p_uiID;
	stIndexInfo.m_uiIndex = page.m_vecUnitList[0].m_uiIndex;
	stIndexInfo.m_uiRefCount = 1;
	m_lstIndexTable.push_back(stIndexInfo);

	return page.m_vecUnitList[0].pszAddress;
}

PageInfo MemoryPool::AllocateGrowBuffer()
{
	unsigned int uiIndex = m_vecPageInfo.size();
	// Re-construct memory pool
	int iCount = m_uiGrowSize / (m_uiUnitSize * m_uiUnitsInPage);
	for(int i = 0; i < iCount; i++)
	{
		// Define a page
		PageInfo stPage;
		char* pszAddress = (char*)malloc(m_uiUnitSize * m_uiUnitsInPage);
		if(NULL == pszAddress)
		{
			LOG(FATAL) << "Cannot allocate memory";
			return PageInfo();
		}

		stPage.pszPageAddress = pszAddress;
		for(int j = 0; j < m_uiUnitsInPage; j++)
		{
			// Define a unit
			UnitInfo stUnit;
			stUnit.m_uiIndex = m_uiMaxIndex;
			stUnit.pszAddress = pszAddress + (j * m_uiUnitSize);

			// Add an unit information to page
			stPage.m_vecUnitList.push_back(stUnit);
			m_uiMaxIndex++;
		}

		// Add a page to memory pool
		m_vecPageInfo.push_back(stPage);
	}

	m_uiMemorySize += m_uiGrowSize;
	m_uiUnitsCount += m_uiGrowSize / m_uiUnitSize;
	return m_vecPageInfo[uiIndex];
}

bool MemoryPool::Release(char* p_szBufferAddress)
{
	if(NULL == p_szBufferAddress)
	{
		return false;
	}

	EnterCriticalSection(&m_csLock);
	
	list<IndexTable>::iterator it = m_lstIndexTable.begin();
	while(it != m_lstIndexTable.end())
	{
		if(it->m_pszAddress == p_szBufferAddress)
		{
			if(it->m_uiRefCount > 1)
			{// Variable minus 1 if reference count is greater than 1
				it->m_uiRefCount--;
				LeaveCriticalSection(&m_csLock);
				return true;
			}
			else
			{// Free memory if reference is 1
				if(false == FreeUnit(it->m_uiIndex))
				{
					m_lstIndexTable.erase(it);
					LeaveCriticalSection(&m_csLock);
					return false;
				}
				else
				{
					m_lstIndexTable.erase(it);
					LeaveCriticalSection(&m_csLock);
					return true;
				}
			}
		}
		it++;
	}

	LeaveCriticalSection(&m_csLock);
	return true;
}

bool MemoryPool::FreeUnit(unsigned int p_uiIndex)
{
	// [NOTE] Can locate buffer position by index, rather than loop for it
	int iCount = m_uiMemorySize / (m_uiUnitSize * m_uiUnitsInPage);
	for(int i = 0; i < iCount; i++)
	{
		for(int j = 0; j < m_uiUnitsInPage; j++)
		{
			if(m_vecPageInfo[i].m_vecUnitList[j].m_uiIndex == p_uiIndex)
			{
				if(m_vecPageInfo[i].m_vecUnitList[j].m_uiUnitCount > 1)
				{
					for(unsigned int uiTemp = 1; uiTemp < m_vecPageInfo[i].m_vecUnitList[j].m_uiUnitCount; uiTemp++)
					{
						m_uiUsedUnits--;
						m_vecPageInfo[i].m_vecUnitList[j+uiTemp].m_bIsAvailable = true;
						m_vecPageInfo[i].m_vecUnitList[j+uiTemp].m_uiUnitCount = 1;
					}
				}

				// Update status of unit
				m_vecPageInfo[i].m_vecUnitList[j].m_uiUnitCount = 1;
				m_vecPageInfo[i].m_vecUnitList[j].m_bIsAvailable = true;
				m_uiUsedUnits--;
				return true;
			}
		}
	}

	LOG(WARNING) << "Cannot find required memory to release";
	return false;
}

bool MemoryPool::IsIDAvailable(unsigned int p_uiID)
{
	EnterCriticalSection(&m_csLock);
	list<IndexTable>::iterator it = m_lstIndexTable.begin();
	while(it != m_lstIndexTable.end())
	{
		if(it->m_uiID == p_uiID)
		{
			LeaveCriticalSection(&m_csLock);
			return false;
		}
		it++;
	}
	LeaveCriticalSection(&m_csLock);
	return true;
}

void MemoryPool::PrintMemoryStatus()
{
	LOG(INFO) << "m_uiMemorySize:" << m_uiMemorySize << " m_uiUnitSize:" << m_uiUnitSize << " m_uiUnitsInPage:" << m_uiUnitsInPage << " m_uiGrowSize:" << m_uiGrowSize << " m_uiMaxIndex:" << m_uiMaxIndex << endl;
	int iCount = m_uiMemorySize / (m_uiUnitSize * m_uiUnitsInPage);
	for(int i = 0; i < iCount; i++)
	{
		LOG(INFO) << "=================================  Page Index : " << i << "  ===============================" << endl;
		for(int j = 0; j < m_uiUnitsInPage; j++)
		{
			LOG(INFO) << "Unit Index:" << m_vecPageInfo[i].m_vecUnitList[j].m_uiIndex << " Available:" << (int)(m_vecPageInfo[i].m_vecUnitList[j].m_bIsAvailable) << "  Unit Count:" << m_vecPageInfo[i].m_vecUnitList[j].m_uiUnitCount << endl;
		}
	}

	LOG(INFO) << "################################### Table Info  ###############################" << endl;
	list<IndexTable> lstTemp(m_lstIndexTable);
	list<IndexTable>::iterator it = lstTemp.begin();
	while(it != lstTemp.end())
	{
		LOG(INFO) << "ID:" << it->m_uiID << " Index:" << it->m_uiIndex << " RefCount:" << it->m_uiRefCount << " Address:" << (unsigned int)it->m_pszAddress << endl;
		it++;
	}
}

bool MemoryPool::ReleaseMemoryPool()
{
	EnterCriticalSection(&m_csLock);
	long int uiVectorSize = m_vecPageInfo.size() - 1;
	while (uiVectorSize >= 0)
	{
		if (!m_vecPageInfo[uiVectorSize].pszPageAddress)
		{
			LeaveCriticalSection(&m_csLock);
			return false;
		}

		free(m_vecPageInfo[uiVectorSize].pszPageAddress);
		m_vecPageInfo.pop_back();
		uiVectorSize--;
	}
	m_lstIndexTable.clear();
	m_uiMaxIndex = 0;
	m_pcMemoryPool = NULL;
	LeaveCriticalSection(&m_csLock);
	return true;
}

float MemoryPool::GetMemoryStatus()
{
	EnterCriticalSection(&m_csLock);
	float fStatus = (float)m_uiUsedUnits / (float)m_uiUnitsCount;
	LeaveCriticalSection(&m_csLock);
	return fStatus;
}